﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using MySql.Data.MySqlClient;
using YmtAuth.Common;
using YmtAuth.Domain.Repository.MySql;
using YmtSystem.CrossCutting;

namespace YmtAuth.Repository.MySQL
{
    /// <summary>
    ///  MySQLRepository base 
    /// </summary>
    /// <remarks>功能未完善</remarks>
    /// <typeparam name="TEntity"></typeparam>
    public class MySQLRepository<TEntity>:IMySQLRepository<TEntity>
    {
        //protected MySqlConnection conn;
        private ConcurrentDictionary<Type, List<string>> paramNameCache = new ConcurrentDictionary<Type, List<string>>();
        private ConcurrentDictionary<Type,object> instanceCache=new ConcurrentDictionary<Type, object>();  
        private readonly string table;
        protected string connString;

        protected ParametersWrapper Parameters
        {
            get { return ParametersWrapper.Factory; }
        }
        public MySQLRepository(string connString, string tableName, bool dbOpen = false)
        {
            //conn = new MySqlConnection(connString);
            this.connString = connString;
            this.table = tableName;
        }
        public virtual TEntity First(dynamic where)
        {
            var tmpEntity = instanceCache.GetOrAdd(typeof (TEntity),
                t => System.Activator.CreateInstance(typeof (TEntity), true));
            var select = GetParamNames((object) tmpEntity);
            if (where == null)
            {
                using (var conn = new MySqlConnection(this.connString))
                {
                    return
                        conn.Query<TEntity>("SELECT " + select.Select(p => p + ",") + " FROM `" + table + "` LIMIT 1")
                            .FirstOrDefault();
                }
            }
            var paramNames = GetParamNames((object) where);
            var w = string.Join(" AND ", paramNames.Select(p => "`" + p + "` = @" + p));
            var selectName = string.Join(" ,", select.Select(p => p));
            var parameters2 = new DynamicParameters(where);
            parameters2.AddDynamicParams(where);
            using (var conn=new MySqlConnection(this.connString))
            {
                return
                    conn.Query<TEntity>("SELECT " + selectName + " FROM `" + table + "` WHERE " + w + " LIMIT 1",
                        parameters2, commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut).FirstOrDefault();
            }          
        }
        public virtual IEnumerable<TEntity> Query(string sql, DynamicParameters parameters, int? timeOut = null, CommandType? commandType = null,
            bool buffered = true)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Query<TEntity>(sql, parameters, null, buffered, timeOut== null?ConfigUtility.LoginSafeSQLCommandTimeOut:timeOut, commandType);
            }
        }
        public virtual IEnumerable<TEntity> Query<T1, T2, TEntity>(string sql, DynamicParameters parameters, Func<T1, T2, TEntity> convert, string splitOn, int? timeOut = null, CommandType? commandType = null,
            bool buffered = true)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Query<T1, T2, TEntity>(sql, convert, parameters, null, buffered, splitOn, timeOut == null ? ConfigUtility.LoginSafeSQLCommandTimeOut : timeOut, commandType);
            }
        }
        public virtual TEntity QueryMultiple(string sql, SqlParameter[] parameters, Func<SqlMapper.GridReader, TEntity> convert, int? timeOut = null, CommandType? commandType = null,
            bool buffered = true)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            var par = parameters.ToDapperParameters();
            using (var conn = new MySqlConnection(this.connString))
            {
                var result = conn.QueryMultiple(sql, par, null, timeOut == null ? ConfigUtility.LoginSafeSQLCommandTimeOut : timeOut, commandType);
                return convert(result);
            }
        }
        public virtual long Insert(TEntity entity)
        {
            YmtSystemAssert.AssertArgumentNotNull(entity, "实体不能为空");
            var o = (object)entity;
            var paramNames = GetParamNames(o);
            paramNames.Remove("Id");

            var cols = string.Join("`,`", paramNames);
            var cols_params = string.Join(",", paramNames.Select(p => "@" + p));
            var sql = "INSERT INTO `" + table + "` (`" + cols + "`) VALUES (" + cols_params + "); SELECT LAST_INSERT_ID()";
            using (var conn = new MySqlConnection(this.connString))
            {
                var id = conn.Query(sql, o, commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut).Single() as IDictionary<string, object>;
                return Convert.ToInt64(id.Values.Single());
            }
        }
        public virtual long Insert(TEntity entity, bool ignoreDuplicate)
        {
            try
            {
                return Insert(entity);
            }
            catch (MySqlException ex)
            {
                if (ignoreDuplicate && ex.Number == 1062)
                    return 0;
                throw;
            }
        }

        public virtual void InsertAsync(TEntity entity, bool ignoreDuplicate)
        {
            Task.Factory.StartNew(() =>
            {
                try
                {
                    return Insert(entity);
                }
                catch (MySqlException ex)
                {
                    if (ignoreDuplicate && ex.Number == 1062)
                        return 0;
                    throw;
                }
            })
                .ContinueWith(t => YmatouLoggingService.Error("insert mysql error {0}", t.Exception),
                    TaskContinuationOptions.OnlyOnFaulted);
        }

        public virtual int Update(IDictionary<string, object> updateColumns, IDictionary<string, object> where)
        {
            YmtSystemAssert.AssertArgumentNotNull(updateColumns, "更新列不能为空");
            YmtSystemAssert.AssertArgumentNotNull(where, "更新条件不能为空");
            var b = new StringBuilder();
            b.Append("UPDATE `").Append(table).Append("` SET ");
            b.AppendLine(string.Join(",", updateColumns.Keys.Select(p => "`" + p + "`= @" + p)));
            b.Append(" WHERE ").Append(string.Join(" AND ", where.Keys.Select(p => "`" + p + "` = @" + p)));
            var parameters = where.ToDapperParameters().ToDapperParameters(updateColumns);
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Execute(b.ToString(), parameters, commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut);
            }
        }
        public virtual int Execute(string sql, IDictionary<string, object> parameter)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql,"SQL 不能为空");
            var tmpparams = parameter.ToDapperParameters();
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Execute(sql, tmpparams, commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut); 
            }
        }

        public virtual int Execute(string sql, SqlParameter[] parameter, int? timeOut = null, CommandType? commandType = null)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            var tmpparams = parameter.ToDapperParameters();
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Execute(sql, tmpparams, commandTimeout: timeOut == null ? ConfigUtility.LoginSafeSQLCommandTimeOut : timeOut, commandType: commandType);
            }
        }
        public virtual bool Delete<TId>(TId id)
        {
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Execute("DELETE FROM `" + table + "` WHERE Id = @id", this.Parameters.Add("@id", id, DbType.Int32).Build(), commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut) > 0;  
            }            
        }
        public virtual bool Delete(dynamic where = null)
        {
            if (where == null)
            {
                using (var conn = new MySqlConnection(this.connString))
                {
                    return conn.Execute("TRUNCATE `" + table + "`") > 0;
                }
            }

            var paramNames = GetParamNames((object)where);
            var w = string.Join(" AND ", paramNames.Select(p => "`" + p + "` = @" + p));
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Execute("DELETE FROM `" + table + "` WHERE " + w, commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut) > 0;
            }
        }
        public virtual int SaveChange(TEntity entity)
        {
            YmtSystemAssert.AssertArgumentNotNull(entity, "要保存的实体不能为空");
            List<string> paramNames = GetParamNames((object)entity);
            string cols = string.Join("`,`", paramNames);
            string cols_params = string.Join(",", paramNames.Select(p => "@" + p));
            string cols_update = string.Join(",", paramNames.Select(p => "`" + p + "` = @" + p));
            var b = new StringBuilder();
            b.Append("INSERT INTO `").Append(table).Append("` (`").Append(cols).Append("`) VALUES (")
             .Append(cols_params).Append(") ON DUPLICATE KEY UPDATE ").Append(cols_update);
            var param = entity.ToParamNames();
            using (var conn = new MySqlConnection(this.connString))
            {
                return conn.Execute(b.ToString(), param, commandTimeout: ConfigUtility.LoginSafeSQLCommandTimeOut);  
            }            
        }

        public virtual void SaveChange(IEnumerable<TEntity> entities)
        {
            entities.Each(e => SaveChange(e));
        }

        public int TrySaveChange(TEntity entity, Action<Exception> errorHandle = null)
        {
            try
            {
                return SaveChange(entity);
            }
            catch (MySqlException ex)
            {
                if (errorHandle != null) errorHandle(ex);
                else YmatouLoggingService.Error("MySQL TrySaveChange ex.", ex);
                return -1;
            }
            catch (Exception ex)
            {
                if (errorHandle != null) errorHandle(ex);
                else YmatouLoggingService.Error("MySQL TrySaveChange ex..", ex);
                return -2;
            }
        }

        protected virtual void Open()
        {
            
        }
        protected virtual void Close()
        {
          
        }       
        List<string> GetParamNames(object o)
        {
            if (o is DynamicParameters)
            {
                return (o as DynamicParameters).ParameterNames.ToList();
            }

            List<string> paramNames;
            if (!paramNameCache.TryGetValue(o.GetType(), out paramNames))
            {
                paramNames = new List<string>();
                foreach (
                    var prop in
                        o.GetType()
                            .GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public))
                {
                    var attribs = prop.GetCustomAttributes(typeof (IgnorePropertyAttribute), true);
                    var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute;
                    if (attr == null || (attr != null && !attr.Value))
                    {
                        paramNames.Add(prop.Name);
                    }
                }
                paramNameCache[o.GetType()] = paramNames;
            }
            return paramNames;
        }
    }
}
